#![no_std]

use core::fmt;

#[cfg(feature = "driver")]
use axplat_aarch64_opi5p::gpio_controller;
pub use gpio_rk3588::Level;

/// Usage lines shared by GPIO command front-ends.
pub const USAGE_LINES: &[&str] = &[
    "Usage: gpio <command> [args]",
    "  gpio set <bank> <pin> <level>",
    "  gpio get <bank> <pin>",
    "  gpio toggle <bank> <pin>",
    "  gpio help",
];

/// Parsed GPIO command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Command {
    Set { bank: usize, pin: u8, level: Level },
    Get { bank: usize, pin: u8 },
    Toggle { bank: usize, pin: u8 },
    Help,
}

/// Result of executing a GPIO command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Response {
    Set { bank: usize, pin: u8, level: Level },
    Get { bank: usize, pin: u8, level: Level },
    Toggle { bank: usize, pin: u8 },
    Help,
}

/// Possible parsing errors when decoding user input.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError<'a> {
    MissingSubcommand,
    MissingArgument(&'static str),
    InvalidBank(&'a str),
    InvalidPin(&'a str),
    InvalidLevel(&'a str),
    UnknownSubcommand(&'a str),
}

impl fmt::Display for ParseError<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::MissingSubcommand => write!(f, "missing subcommand"),
            ParseError::MissingArgument(name) => write!(f, "missing {name} argument"),
            ParseError::InvalidBank(token) => {
                write!(f, "invalid bank '{token}' (expected 0-4)")
            }
            ParseError::InvalidPin(token) => {
                write!(f, "invalid pin '{token}' (expected 0-31)")
            }
            ParseError::InvalidLevel(token) => {
                write!(f, "invalid level '{token}' (use high/low/on/off/1/0)")
            }
            ParseError::UnknownSubcommand(token) => write!(f, "unknown subcommand '{token}'"),
        }
    }
}

/// Parse a GPIO command by subcommand name and argument iterator.
pub fn parse_command<'a, I>(subcmd: &'a str, mut args: I) -> Result<Command, ParseError<'a>>
where
    I: Iterator<Item = &'a str>,
{
    match subcmd {
        "set" => {
            let bank_str = args.next().ok_or(ParseError::MissingArgument("bank"))?;
            let pin_str = args.next().ok_or(ParseError::MissingArgument("pin"))?;
            let level_str = args.next().ok_or(ParseError::MissingArgument("level"))?;

            let bank = parse_bank(bank_str).ok_or(ParseError::InvalidBank(bank_str))?;
            let pin = parse_pin(pin_str).ok_or(ParseError::InvalidPin(pin_str))?;
            let level = parse_level(level_str).ok_or(ParseError::InvalidLevel(level_str))?;

            Ok(Command::Set { bank, pin, level })
        }
        "get" => {
            let bank_str = args.next().ok_or(ParseError::MissingArgument("bank"))?;
            let pin_str = args.next().ok_or(ParseError::MissingArgument("pin"))?;

            let bank = parse_bank(bank_str).ok_or(ParseError::InvalidBank(bank_str))?;
            let pin = parse_pin(pin_str).ok_or(ParseError::InvalidPin(pin_str))?;

            Ok(Command::Get { bank, pin })
        }
        "toggle" => {
            let bank_str = args.next().ok_or(ParseError::MissingArgument("bank"))?;
            let pin_str = args.next().ok_or(ParseError::MissingArgument("pin"))?;

            let bank = parse_bank(bank_str).ok_or(ParseError::InvalidBank(bank_str))?;
            let pin = parse_pin(pin_str).ok_or(ParseError::InvalidPin(pin_str))?;

            Ok(Command::Toggle { bank, pin })
        }
        "help" => Ok(Command::Help),
        other => Err(ParseError::UnknownSubcommand(other)),
    }
}

/// Execute a parsed GPIO command and return the resulting response.
#[cfg(feature = "driver")]
pub fn execute(command: Command) -> Response {
    match command {
        Command::Set { bank, pin, level } => {
            gpio_controller().set_output(bank, pin, level);
            Response::Set { bank, pin, level }
        }
        Command::Get { bank, pin } => {
            let level = gpio_controller().read_input(bank, pin);
            Response::Get { bank, pin, level }
        }
        Command::Toggle { bank, pin } => {
            gpio_controller().toggle(bank, pin);
            Response::Toggle { bank, pin }
        }
        Command::Help => Response::Help,
    }
}

/// Convert a level to a printable string.
pub fn display_level(level: Level) -> &'static str {
    match level {
        Level::High => "HIGH",
        Level::Low => "LOW",
    }
}

fn parse_bank(token: &str) -> Option<usize> {
    parse_usize(token).filter(|&value| value < 5)
}

fn parse_pin(token: &str) -> Option<u8> {
    parse_u8(token).filter(|&value| value < 32)
}

fn parse_level(token: &str) -> Option<Level> {
    if token.eq_ignore_ascii_case("high") || token.eq_ignore_ascii_case("on") || token == "1" {
        Some(Level::High)
    } else if token.eq_ignore_ascii_case("low") || token.eq_ignore_ascii_case("off") || token == "0"
    {
        Some(Level::Low)
    } else {
        None
    }
}

fn parse_usize(token: &str) -> Option<usize> {
    if let Some(hex) = token
        .strip_prefix("0x")
        .or_else(|| token.strip_prefix("0X"))
    {
        usize::from_str_radix(hex, 16).ok()
    } else {
        token.parse().ok()
    }
}

fn parse_u8(token: &str) -> Option<u8> {
    if let Some(hex) = token
        .strip_prefix("0x")
        .or_else(|| token.strip_prefix("0X"))
    {
        u8::from_str_radix(hex, 16).ok()
    } else {
        token.parse().ok()
    }
}
